/**
**********************************************************************
*   Copyright (C) 2021 wy1107412334@163.com All rights reserved.
*
* @file     main.c
* @brief    对文件的简要说明
* @details  对文件的详细说明
* @author   jcai
* @Version  v0.0.1
* @date     2021-06-27 12:23:19
*
*   Email:  wy1107412334@163.com
*   Blog:   None
*   Github: https://github.com/wy1107412334
*   Gitee:  https://gitee.com/git.git1107412334.com
*
**********************************************************************
@verbatim
@endverbatim
**********************************************************************
* @note
* None.
**********************************************************************
* @attention
* None.
**********************************************************************
* @par 历史记录
*  1. V0.0.1: jcai, 2021-06-27 12:23:19
*    - 首次编写
*
**********************************************************************
*/

/* 头文件导入区 --------------------------------------------------- */

#define _XOPEN_SOURCE 500
#include <ftw.h>
#include <stdint.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <getopt.h>
#include <dirent.h>
#include <strings.h>
#include <string.h>
#include <stddef.h>
#include <errno.h>
#include <arpa/inet.h>
#include <setjmp.h>
#include <libgen.h>
#include "double_link_list.h"


/** @defgroup MAIN MAIN
* @brief main modules
* @{
*/



/*********************************************************************
                私有宏定义
*********************************************************************/


#ifdef DEBUG
#ifdef LINENO
#define printErr(fmt, ...)  fprintf(stderr, "%s(%u): " fmt, __FILE__, __LINE__, ##__VA_ARGS__)//, exit(100)
#define printDbg(fmt, ...)  fprintf(stdout, " Debug: %s(%u): " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#else
#define printErr(fmt, ...)  fprintf(stderr, fmt, ##__VA_ARGS__)
#define printDbg(fmt, ...)  fprintf(stdout, " Debug: %s(%u): " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#endif
#else
#define printErr(fmt, ...)
#define printDbg(fmt, ...)
#endif

#define printInf(fmt, ...)   fprintf(stdout, fmt, ##__VA_ARGS__)

#define printCls(fmt, len, ...)     printf("\e[%dD" fmt "\e[0m", len, ##__VA_ARGS__)

#define READ_BLOCK_SIZE  1024

/**
 * 最大路径长度。不包括字符串结束符
 */
#define MAX_PATH_LEN    4096
/**
 * ROMFS镜像中支持的最大文件名长度。包括字符串结束符
 */
#define MAX_NAME_LEN    255


/**
 * ROMFS镜像的两种魔术字
 */
#define RFS1    "RFS1"
#define RFS2    "RFS2"

/**
 * 将x向上对齐到align的边界
 */
#define ROUND_UP(x, align) (((long) (x) + (align - 1)) & ~(align - 1))
#define ALIGN_UP4(x) ROUND_UP(x, 4)
/**
 * 将ROMFS镜像中的每个entry的大小向上对齐到4的边界
 */
#define ENTRY_LEN(len) ALIGN_UP4(offsetof(struct _entry, name) + len)





/**
 * 通过结构体字段的地址来获取其结构体首地址。
 */
#define STRUCT_ADDR(field_addr, type, field) ((type *)((char *)(field_addr) - offsetof(type, field)))



/*********************************************************************
                私有数据类型声明
*********************************************************************/


/**
 * 保存ROMFS镜像的魔术字的结构
 */
union rfs_magic {
    uint8_t buf[4];
    uint32_t magic;
};
/**
 * ROMFS镜像文件的头结构
 */
struct rfs_head {
    union rfs_magic magic;  /*! 镜像的魔术字 */
    uint32_t size;          /*! 镜像的大小。仅在魔术字为 RFS2 时使用 */
    uint8_t  cksum[16];     /*! 保留。仅在魔术字为 RFS2 时使用 */
};

/**
 * ROMFS镜像中目录文件中的entry结构
 * 目录中的所有entry的首地址都是对齐在4的边界
 */
struct _entry {
    uint32_t offset;                /*! 文件的inode在镜像中的偏移 */
    uint8_t  mode;                  /*! 文件的类型。0x04目录，0x08文件 */
    uint8_t  namelen;               /*! 文件名的长度，不包括字符串结束符 */
    uint8_t  name[MAX_NAME_LEN];    /*! 保存文件名，包括字符串结束符 */
};

struct _inode {
    uint32_t rf_size;   /*! 文件的内容的大小 */
    uint16_t rf_mode;   /*! 文件的类型及其访问模式 */
    uint16_t rf_nlink;  /*! 保留 */
    struct {
    uint32_t tv_sec;    /*! 文件的最后修改时间。
                         * 从 1970/01/01 00:00:00 至今的秒数
                         */
    uint32_t tv_nsec;   /*! 保留 */
    }        rf_ctime;
    uint32_t rf_flags;  /*! 保留 */
    uint32_t rf_uid;    /*! 文件用户id */
    uint32_t rf_gid;    /*! 文件组id */
    uint32_t rf_rdev;   /*! 保留 */
    uint32_t rf_space;  /*! 和 rf_size 的值一样 */
};

/**
 * 每个文件在ROMFS镜像中的所需要的所有数据
 */
struct file_content {
    struct _inode   inode;      /*! 本文件的inode信息 */
    void           *pContent;   /*! 指向文件内容。
                                 *  如果是普通文件，则保存文件内容；
                                 *  如果是目录文件，则保存文件的entry结构
                                 */
    struct _entry  *next;       /*! 如果是目录的话，指向 pContent 中
                                 *  下一个 entry 的首地址。
                                 *  如果是文件的话，本元素一直等于 pContent的值
                                 */
    struct _entry **entries;    /*! 如果是目录的话，这是一个元素数量为 entryNum
                                 *  的数组，每个元素指向本目录中包含的
                                 *  文件的entry在 pContent 中的开始位置，
                                 *  包括 "." 和 ".."。
                                 *  如果是文件的话，本元素一直为NULL
                                 */
    uint32_t        entryNum;   /*! entries 中元素数量(包括 . 和 ..) */
    uint32_t        usedLen;    /*! pContent 指向的缓冲区实际使用的长度。字节 */
    uint32_t        totalLen;   /*! pContent 指向的缓冲区的最大长度。字节 */
    uint32_t        offset;     /*! 本文件的inode数据在镜像文件中的偏移 */
};

/**
 * 保存文件/目录的所有信息
 */
struct dentry {
    DL_NODE             node;       /*! 全局线性链表中下/上一个节点的地址 */
    struct dentry      *parent;     /*! 父目录。如果是根目录，则为NULL */
    DL_NODE             childNode;  /*! 下/上一个同级的文件的地址。 */
    DL_LIST             child;      /*! 如果是目录，那么其中保存了目录中保存的
                                     *  文件在内存中的节点地址。其指向 childNode
                                     *  元素。其中不保存目录的 "." 和 ".." 文件
                                     *  如果是文件，则链表长度一直为0 */
    struct file_content content;    /*! 本文件在ROMFS镜像文件中的内容 */
    char    filename[MAX_NAME_LEN]; /*! 在ROMFS镜像中的文件名 */
    char    path[MAX_PATH_LEN + 1]; /*! 本文件在主机系统中的绝对路径 */
    struct stat         stat;       /*! 本文件在主机磁盘中的元数据信息 */
    int                 level;      /*! 文件/目录层级。 根目录为0 */
};




/*********************************************************************
                私有变量定义
*********************************************************************/

/**
 * 指向要生成ROMFS文件系统的目录在系统中的绝对路径
 */
static const char * srcDirectory = NULL;
/**
 * 指向保存要生成的ROMFS文件系统的数据的文件的文件名
 */
static const char * outFilename = "content.romfs";
/**
 * 指向保存ROMFS文件系统镜像的文件在系统中的绝对路径
 */
static char       * outFilePath = NULL;
/**
 * 指向一个保存ROMFS文件系统的镜像的文件的文件指针
 */
static FILE       * out = NULL;
/**
 * 保存要生成ROMFS文件系统的目录下所有文件信息的链表
 */
static DL_LIST      gList;

static const char *rfsHead = RFS1; /*! 生成的ROMFS镜像使用哪种RFS头 */
static int rfsLen = sizeof(union rfs_magic);
static const struct option longopts[] = {
    {"output", required_argument, NULL, 'o'},
    {"RFS2", no_argument, NULL, 's'},
    {"rfs2", no_argument, NULL, 's'},
    {"help", no_argument, NULL, 'h'},
    {NULL, 0, NULL, 0},
};

/**
 * 保存错误恢复栈
 */
static jmp_buf jmpBuf;




/** @defgroup MAIN_Exported_Variables main exported variables
* @brief 全局变量定义
* @{
*/



/** defgroup MAIN_Exported_Variables
* @}
*/





/*********************************************************************
                私有函数定义
*********************************************************************/

static void getopts(int argc, char **argv)
{
    int ch;
    int ret;
    struct stat rootstat;
    char *p;

    opterr = 1;
    do
    {
        ch = getopt_long(argc, argv, ":o:sh", longopts, NULL);
        switch (ch)
        {
        case 'o':
            if (!optarg || '\0' == *optarg)
            {
                fprintf(stderr, "-o选项不能为NULL或空字符串\n");
                goto helpinfo;
            }
            outFilename = optarg;
            break;
        case 's':
            rfsHead = RFS2;
            rfsLen = sizeof(struct rfs_head);
            break;
        case ':':
        case '?':
        default:
        case 'h':
helpinfo:
            printf("将 directory 目录及其中的所有文件使用romfs文件系统格式保存。\n"
                   "用法: %s [-o <file>] [-s] directory\n选项说明:\n"
                   "\t -o <file>, --output=<file>\t输出文件名。默认输出文件名: %s\n"
                   "\t -s, --rfs2, --RFS2\t\tROMFS镜像使用%s魔术字。默认使用%s魔术字。\n"
                   "\t -h, --help\t\t\t本帮助信息\n",
                   *argv, outFilename, RFS2, RFS1);
            exit(1);
            break;

        case -1:
            break;
        }
    } while (ch != -1);

    if (*(argv + optind))
    {
        srcDirectory = *(argv + optind);
    }
    else
    {
        printErr("缺少源目录\n");
        goto helpinfo;
    }

    ret = lstat(srcDirectory, &rootstat);
    if (ret)
    {
        perror("stat");
        exit(2);
    }
    if (!S_ISDIR(rootstat.st_mode))
    {
        printErr("输入源目录不是一个目录文件\n");
        exit(3);
    }

    p = (char *)malloc(sizeof(char) * (MAX_PATH_LEN + 1) * 2);
    if (!p)
    {
        printErr("内存不足! 无法分配保存源目录绝对路径的内存\n");
        exit(4);
    }
    outFilePath = (char *)(p + sizeof(char) * (MAX_PATH_LEN + 1));

    // 打开输出文件
    out = fopen(outFilename, "w");
    if (!out)
    {
        printErr("打开/创建输出文件 '%s' 失败！\n", outFilename);
        exit(5);
    }

    if (!realpath(srcDirectory, p) || !realpath(outFilename, outFilePath))
    {
        printErr("获取源目录的绝对路径失败!\n");
        free(p);
        remove(outFilename);
        exit(6);
    }
    srcDirectory = p;

    printInf("\n----------------------------------------\n");
    printInf("输入目录为：%s\n", srcDirectory);
    printInf("输出文件为：%s\n", outFilePath);
    printInf("ROMFS镜像使用的版本为：%s\n", rfsHead);
    printInf("ROMFS镜像头大小为：%d\n", rfsLen);
    printInf("----------------------------------------\n");
}


static int rfs_fill_file(struct dentry *pdt)
{
    FILE *pf;
    void *ptr;
    int ret, i = 0;
    int size = 1;
    int len = pdt->stat.st_size;

    pf = fopen(pdt->path, "r");
    if (!pf)
    {
        printErr("打开文件 '%s' 读取内容失败！\n", pdt->path);
        return 1;
    }

    do
    {
        size = (len >= READ_BLOCK_SIZE) ? READ_BLOCK_SIZE : 1;
        ptr = (void *)((char *)pdt->content.pContent + i);
        ret = fread(ptr, size, 1, pf);
        if (ret < 0)
        {
            printInf("\n\e[1;31m读取文件 '%s' 错误：%s\e[0m\n",
                     pdt->path, strerror(errno));
            break;
        }
        len -= ret * size;
        i += ret * size;
        ret = 0;
    } while (len);
    if (!len)   ret = 0;

    fclose(pf);
    return ret;
}
static int rfs_fill_symbolic_file(struct dentry *pdt)
{
#if 1
    return rfs_fill_file(pdt);
#else
    int len = pdt->stat.st_size;
    int ret = (int)readlink(pdt->path, pdt->content.pContent, len);
    if (ret < 0)
    {
        printInf("\n\e[1;31m读取文件 '%s' 错误：%s\e[0m\n",
                 pdt->path, strerror(errno));
    }
    else if (ret != len)
    {
        printInf("\n\e[1;31m文件 '%s' 内容被截断：%s\e[0m\n",
                 pdt->path, strerror(errno));
    }
    else    ret = 0;

    return ret;
#endif
}
static int rfs_fill_other_file(struct dentry *pdt)
{
    (void)pdt;
    return 0;
}
static void rfs_fill_entry(struct dentry *pdt, const struct _entry *pe)
{
    int len = ENTRY_LEN(pe->namelen);
    memcpy(pdt->content.next, pe, len);
    pdt->content.next = (struct _entry *)((char *)pdt->content.next + len);
    pdt->content.usedLen += len;
}
static int rfs_fill_directory(struct dentry *pdt)
{
    struct dentry *t;
    struct _entry e;
    int i, len;

    // 目录自身的entry
    e.offset = htonl(pdt->content.offset);
    e.mode = S_IFDIR >> 12;
    e.namelen = 1;
    bzero(e.name, MAX_NAME_LEN);
    strcpy((char *)e.name, ".");
    *(pdt->content.entries + 0) = pdt->content.next;
    rfs_fill_entry(pdt, &e);
    // 父目录的entry
    e.offset = pdt->parent ? htonl(pdt->parent->content.offset) :
                             htonl(pdt->content.offset);
    e.mode = S_IFDIR >> 12;
    e.namelen = 2;
    bzero(e.name, MAX_NAME_LEN);
    strcpy((char *)e.name, "..");
    *(pdt->content.entries + 1) = pdt->content.next;
    rfs_fill_entry(pdt, &e);

    len = pdt->content.entryNum - 2;
    for (i = 0; i < len; i++)
    {
        t = STRUCT_ADDR(dllist_at(&pdt->child, i), struct dentry, childNode);
        // 这里的偏移值需要等待处理本目录中的文件/目录的时候才会来此填入
        // 其真实的偏移
        e.offset = 0;
        e.mode = (t->stat.st_mode & S_IFMT) >> 12;
        e.namelen = strlen(t->filename);
        bzero(e.name, MAX_NAME_LEN);
        strcpy((char *)e.name, t->filename);
        // 保存当前entry在pContent中的首地址
        *(pdt->content.entries + 2 + i) = pdt->content.next;
        rfs_fill_entry(pdt, &e);
    }
    // 再加上目录的结束标记，
    // 由于 pdt->content.pContent 中在初始时已经全填充了0，
    // 并且目录结束标记的值也是0，所以这里只是将内容长度加4就可以了
    pdt->content.usedLen += 4;
    return 0;
}

static void rfs_fill_content(struct dentry *pdt)
{
    int (*fillContent)(struct dentry *);
    uint8_t *p;
    uint32_t sum;
    int ret;

    // 对目录的处理
    if (S_ISDIR(pdt->stat.st_mode))
    {
        struct _entry **pe;
        int childCnt = dllist_length(&pdt->child);
        if (childCnt < 0)   // 不应该出现的错误
        {
            fprintf(stderr,
                    "\e[1;33m异常\e[0m 获取目录'%s'中文件数量返回负数。\n",
                    pdt->path);
            longjmp(jmpBuf, -1);
        }
        // 加上2的原因是，还需要保存名为 "." 和 ".." 的目录
        childCnt += 2;

        // 最后再加4的原因是由于目录内容的结束标记是4个字节的0
        sum = ALIGN_UP4(sizeof(struct _entry) * childCnt + 4);
        pdt->content.usedLen = 0;
        fillContent = rfs_fill_directory;

        pe = (struct _entry **)malloc(childCnt * sizeof(struct _entry *));
        if (!pe)
        {
            printErr("内存不足。");
            longjmp(jmpBuf, -1);
        }
        bzero(pe, childCnt * sizeof(struct _entry *));
        pdt->content.entries = pe;
        pdt->content.entryNum = childCnt;
    }
    // 对普通文件的处理
    else if (S_ISREG(pdt->stat.st_mode))
    {
        sum = ALIGN_UP4(pdt->stat.st_size);
        pdt->content.usedLen = sum;
        fillContent = rfs_fill_file;
    }
    // 对链接文件的处理
    else if (S_ISLNK(pdt->stat.st_mode))
    {
        sum = ALIGN_UP4(pdt->stat.st_size);
        pdt->content.usedLen = sum;
        fillContent = rfs_fill_symbolic_file;
    }
    // 对其他文件的处理
    else
    {
        sum = 0;
        pdt->content.usedLen = sum;
        fillContent = rfs_fill_other_file;
    }

    p = (uint8_t *)malloc(sum);
    if (!p)
    {
        printErr("内存不足。无法申请创建文件内容的存储空间。\n");
        longjmp(jmpBuf, -2);
    }

    bzero(p, sum);
    pdt->content.totalLen = sum;
    pdt->content.pContent = (void *)p;
    pdt->content.next = (struct _entry *)p;

    // 如果本文件/目录有父目录，那么需要在父目录的entry中填入本文件/目录的
    // inode在romfs镜像中的偏移
    if (pdt->parent)
    {
        int i;
        // 获取本文件/目录所在的节点的上一个节点的地址
        // 上一个节点的偏移 + 上一个节点的文件/目录的内容的大小 + inode的大小
        // 最后将上面的结果向上对齐到4的边界即为本文件/目录的偏移
        struct dentry * pdt2 = STRUCT_ADDR(DL_PREV_NODE(&pdt->node),
                                                        struct dentry, node);
        pdt->content.offset = ALIGN_UP4(pdt2->content.offset +
                                        sizeof(struct _inode) +
                                        pdt2->content.usedLen);

        // 得到本文件/目录的父目录，并且在父目录中的本文件/目录对应的entry
        // 中填入当前文件/目录的偏移量
        pdt2 = pdt->parent;
        i = dllist_addr2at(&pdt2->child, &pdt->childNode);
        if (i >= 0)
        {
            struct _entry *pe = *(pdt2->content.entries + 2 + i);
            pe->offset = htonl(pdt->content.offset);
        }
        else  // 不应该出现的错误
            fprintf(stderr,
                    "\n\e[1;33m异常\e[0m 文件不存在父目录的子文件链表中。\n"
                    "\tparent: %s, child: %s\n", pdt2->path, pdt->path);
    }
    // 不存在父目录那么，说明为根目录，那么根目录的偏移量设置为ROMFS头的大小
    else pdt->content.offset = rfsLen;

    ret = fillContent(pdt);
    printDbg("%s, offset: %u, size: %u\n",
             pdt->path, pdt->content.offset, pdt->content.usedLen);
    if (ret)
    {
        printErr("读取文件内容填充到内存中失败。\n");
        longjmp(jmpBuf, -3);
    }
}


static void rfs_stat2inode(struct dentry *pdt)
{
    if (S_ISDIR(pdt->stat.st_mode))
        pdt->content.inode.rf_size = htonl(pdt->content.usedLen);
    else
        pdt->content.inode.rf_size = htonl(pdt->stat.st_size);
    pdt->content.inode.rf_nlink = 0;
    // VxWorks的mkromfs工具生成的镜像中这里就是0
    pdt->content.inode.rf_ctime.tv_sec = 0;//htonl(pdt->stat.st_mtime);
    pdt->content.inode.rf_ctime.tv_nsec = 0;
    pdt->content.inode.rf_flags = 0;
    pdt->content.inode.rf_uid = htonl(pdt->stat.st_uid);
    pdt->content.inode.rf_gid = htonl(pdt->stat.st_gid);
    pdt->content.inode.rf_rdev = 0;
    // VxWorks的mkromfs工具生成的镜像中这里：
    // 如果是目录那么值则是 rf_size
    // 否则值则是 文件的内容的长度 + 2
    if (S_ISREG(pdt->content.inode.rf_mode))//if (S_ISREG(pdt->stat.st_mode))
        pdt->content.inode.rf_space = htonl(pdt->stat.st_size + 2);
    else if (S_ISLNK(pdt->content.inode.rf_mode))//else if (S_ISLNK(pdt->stat.st_mode))
        pdt->content.inode.rf_space = htonl(pdt->stat.st_size + 3);
    else
        pdt->content.inode.rf_space = pdt->content.inode.rf_size;
    pdt->content.inode.rf_mode = htons(pdt->stat.st_mode);
}

static void updateFileData(const DL_NODE *pNode)
{
    static uint32_t i = 1;
    struct dentry * pdt = STRUCT_ADDR(pNode, struct dentry, node);

    printCls("\t\tNumber of files processed: %5u", 40+50, i++);
    rfs_fill_content(pdt);
    rfs_stat2inode(pdt);
}


static void releaseNode(const DL_NODE *pNode)
{
    struct dentry * pdt = STRUCT_ADDR(pNode, struct dentry, node);
    if (pdt->content.pContent) free(pdt->content.pContent);
    if (pdt->content.entries)  free(pdt->content.entries);
    free((void *)pdt);
}


#ifdef DEBUG
static void output_child_filename(const DL_NODE *pNode)
{
    struct dentry * pdt = STRUCT_ADDR(pNode, struct dentry, childNode);
    int i;

    // 输出缩进
    for (i = 0; i < pdt->level; i++)    printf("  ");

    // 是目录
    if (S_ISDIR(pdt->stat.st_mode))
        printf("%s/ (%d)\n", pdt->filename, dllist_length(&pdt->child));
    else
        printf("%s\n", pdt->filename);

    // 遍历子目录
    dllist_traverse(&pdt->child, output_child_filename);
}

static void traverse_output_tree(DL_NODE *pRootNode)
{
    struct dentry * pdt = STRUCT_ADDR(pRootNode, struct dentry, node);
    int i;

    // 输出缩进
    for (i = 0; i < pdt->level; i++)    printf("  ");

    // 是目录
    if (S_ISDIR(pdt->stat.st_mode))
    {
        // 是根目录
        if (!pdt->parent)
            printf("根目录：%s/ (%d)\n", pdt->path, dllist_length(&pdt->child));
        // 不是根目录
        else
            printf("%s/ (%d)\n", pdt->filename, dllist_length(&pdt->child));

        // 遍历子目录
        dllist_traverse(&pdt->child, output_child_filename);
    }
    // 是文件
    else
        printf("%s\n", pdt->filename);
}
#endif /* DEBUG */



static int rfs_write(const uint8_t *buf, int len)
{
    int ret, i = 0;
    int cnt = 0;

    do
    {
        ret = fwrite(buf + i, 1, 1, out);
        if (ret < 0)    return i;
        len -= ret;
        i += ret;
        if (!ret && cnt++ >= 3) return -1;
    } while (len);
    return i;
}

static int rfs_writeData(int offset, const uint8_t *buf, int len)
{
    int ret = fseek(out, offset, SEEK_SET);
    if (ret)    return ret;

    if (len <= 0)   return -1;

    return rfs_write(buf, len);
}

static int rfs_start(void)
{
    struct rfs_head head;

    memcpy(head.magic.buf, rfsHead, rfsLen);
    if (rfs_writeData(0, head.magic.buf, rfsLen) != rfsLen)
        return 1;
    return 0;
}
static int rfs_end(void)
{
    struct rfs_head head;
    uint32_t size = (uint32_t)ftell(out);

    memcpy(head.magic.buf, rfsHead, rfsLen);
    head.size = htonl(size);
    bzero(head.cksum, sizeof(head.cksum));
    if (rfs_writeData(0, head.magic.buf, rfsLen) != rfsLen)
        return 1;
    return 0;
}



static void output2file(const DL_NODE *pNode)
{
    static uint32_t i = 1;
    uint8_t *p;
    struct dentry * pdt = STRUCT_ADDR(pNode, struct dentry, node);
    int len = sizeof(pdt->content.inode) + pdt->content.usedLen;
    int ret;

    printCls("\t\tNumber of output files: %5u", 37+13/*50*/, i++);

    p = (uint8_t *)malloc(len);
    if (!p)
    {
        printErr("内存不足！\n");
        longjmp(jmpBuf, -4);
    }
    memcpy(p, &pdt->content.inode, sizeof(pdt->content.inode));
    memcpy(p + sizeof(pdt->content.inode), pdt->content.pContent,
           pdt->content.usedLen);

    if (pdt->content.offset != ftell(out))  // 不应该出现的错误
        fprintf(stderr,
                "\e[1;33m异常\e[0m file: %s, doffset: %u, foffset: %lu\n",
                pdt->path, pdt->content.offset, ftell(out));

    ret = rfs_writeData(pdt->content.offset, p, len);
    if (ret != len)
    {
        printErr("写文件错误！\n");
        free(p);
        longjmp(jmpBuf, -5);
    }
    free(p);
    printDbg("file: %s\n\tsize: %u(%lu)\ttype: %s\toffset: %u\n",
             pdt->path, pdt->content.usedLen, pdt->stat.st_size,
             (S_ISDIR(pdt->stat.st_mode)) ? "目录" : "文件",
             pdt->content.offset);
}

static int rfs_output_image(void)
{
    printInf("\t1. 包括目录共 %d 个文件。共6个步骤。(1/6)\n", dllist_length(&gList));
    printInf("\t2. 开始根据各文件节点数据生成其在ROMFS镜像中的数据。(2/6)\n");
    if (!setjmp(jmpBuf))
    {
        // 生成所有文件的数据到各自的节点中
        dllist_traverse(&gList, updateFileData);
    }
    else
    {
        printInf("\t\t2.1. 根据各文件节点数据生成其在ROMFS镜像中的数据的操作失败。(2/6)\n");
        return 1;
    }

    printInf("\n\t3. 开始生成镜像文件头。(3/6)\n");
    // 生成镜像文件头
    if (rfs_start())    return 2;

    printInf("\t4. 开始将各文件节点中的数据写入到ROMFS镜像文件中。(4/6)\n");
    if (!setjmp(jmpBuf))
    {
        // 将节点中的数据输出到镜像中
        dllist_traverse(&gList, output2file);
    }
    else
    {
        printInf("\t\t4.1. 将各文件节点中的数据写入到ROMFS镜像文件中的操作失败。(4/6)\n");
        return 3;
    }

    printInf("\n\t5. 开始更新镜像文件头信息。(5/6)\n");
    // 更新镜像文件头
    if (rfs_end())    return 4;

    printInf("\t6. OK。共 %d 个文件 (6/6)\n", dllist_length(&gList));
    return 0;
}

static int32_t find_parent(const DL_NODE *pNode, void *arg)
{
    struct dentry * pdt = STRUCT_ADDR(pNode, struct dentry, node);
    return !strcmp(pdt->path, (const char *)arg);
}

static int nftw_fn (const char *fpath, const struct stat *sb,
          int typeflag, struct FTW *ftwbuf)
{
    static uint32_t i = 0;
    char *pPath = NULL;
    struct dentry *pdt = NULL;
    struct dentry *parent;
    struct stat tst;
    int err = 0;
    DL_NODE *t;
    char *pbn;
    char *pdn;

    printCls("\t\tNumber of output files: %5u", 37+13/*50*/, i++);

    // 如果要生成的ROMFS镜像文件是在本目录中，那么则跳过该文件
    if (!strcmp(fpath, outFilePath))
    {
        printInf("\n\t\e[1;31m跳过要生成的ROMFS镜像文件：%s\e[0m\n",
                 outFilePath);
        return 0;
    }

    pPath = (char *)malloc(MAX_PATH_LEN + 1);
    if (!pPath)
    {
        printInf("\n\t\e[1;31mmalloc()失败\e[0m\n");
        err = -1;
        goto nftw_fn_err;
    }
    pdt = (struct dentry *)malloc(sizeof(*pdt));
    if (!pdt)
    {
        printInf("\n\t\e[1;31mmalloc()失败\e[0m\n");
        err = -1;
        goto nftw_fn_err;
    }
    bzero(pdt, sizeof(*pdt));
    memcpy(&pdt->stat, sb, sizeof(*sb));
    pdt->level = ftwbuf->level;

    if (strlen(fpath) > MAX_PATH_LEN)
    {
        printInf("路径 '%s' 太长。最大支持 %d 字节\n",
                fpath, MAX_PATH_LEN + 1);
        err = -2;
        goto nftw_fn_err;
    }
    strcpy(pdt->path, fpath);
    strcpy(pPath, fpath);
    pbn = basename(pPath);
    pdn = dirname(pPath);
    if (strlen(pbn) >= MAX_NAME_LEN)
    {
        printInf("文件 '%s' 名称太长。最大支持 %d 字节\n",
                 pbn, MAX_NAME_LEN - 1);
        err = -2;
        goto nftw_fn_err;
    }
    if (!ftwbuf->level)
        strncpy(pdt->filename, "/", 2);
    else
        strncpy(pdt->filename, pbn, MAX_NAME_LEN);

    switch (typeflag)
    {
    /* 普通文件 */
    case FTW_F:
        break;

    /* 目录 */
    case FTW_D:
        break;

    /* 符号链接 */
    case FTW_SL:
        if (stat(fpath, &tst))
        {
            printInf("\n\t\e[1;31m跳过链接文件：%s, "
                     "其链接目标不存在\e[0m\n", fpath);
            //err = -3;
            free(pdt);
            goto nftw_fn_err;
        }
        memcpy(&pdt->stat, &tst, sizeof(tst));
        break;

    default:
        printInf("\n\t\e[1;31m文件 '%s' 是未知文件类型：%d\e[0m\n",
                 fpath, typeflag);
        err = -4;
        goto nftw_fn_err;
        break;
    }

    dllist_insert_tail(&gList, &pdt->node);
    dllist_static_init(&pdt->child);
    t = dllist_find(&gList, find_parent, pdn);
    if (!t)
    {
        if (ftwbuf->level)
        {
            printInf("没有找到文件 '%s' 的父目录\n", pdt->filename);
            err = -5;
            goto nftw_fn_err;
        }
    }
    else
    {
        parent = STRUCT_ADDR(t, struct dentry, node);
        pdt->parent = parent;
        dllist_insert_tail(&parent->child, &pdt->childNode);
    }

nftw_fn_err:
    if (pPath)  free(pPath);
    if (err && pdt)    free(pdt);

    return err;
}

/** @defgroup MAIN_Exported_Functions main exported functions
* @brief 全局函数定义
* @{
*/

/**
**********************************************************************
* @brief    简要说明
* @details  详细说明
* @param    None.
* @param[in]     a: 该参数用于输入
* @param[in,out] b: 该参数用于输入和输出
* @param[out]    c: 该参数用于输出
* @retval  None.
* @retval  0 函数成功
* @retval  1 函数失败
* @return  返回0成功；否则失败
* @note    None.
* @par 历史记录
*  1. V0.0.1: jcai, 2021-06-27 12:23:19
*    - 首次编写
*/
int main(int argc, char *argv[])
{
    int ret;

    // 初始化全局链表。用于保存所有要处理的文件
    dllist_static_init(&gList);

    // 处理输入参数
    getopts(argc, argv);

    printInf("\n开始递归读取要输出的目录: %s\n", srcDirectory);
    ret = nftw(srcDirectory, nftw_fn, 1, FTW_PHYS);
    if (ret)
    {
        printErr("递归读取文件失败！\n");
        ret = -3;
        goto err;
    }

#ifdef DEBUG
    printInf("\n要输出的文件如下：\n");
    traverse_output_tree(dllist_at(&gList, 0));
    printInf("\n要输出的文件 结束\n");
#endif

    printInf("\n开始生成镜像文件中。\n");
    ret = rfs_output_image();
    if (ret)
    {
        ret = -4;
        printInf("生成镜像文件失败！释放资源中。\n");
    }
    else
        printInf("镜像文件生成完成，释放资源中。\n");

err:
    // 释放资源
    if (srcDirectory)
        free((void *)srcDirectory);
    dllist_destroyCallback(&gList, releaseNode);
    if (out)
    {
        fclose(out);
        if (ret)    remove(outFilename);
    }

    return ret;
}


/** defgroup MAIN_Exported_Functions
* @}
*/

/** defgroup MAIN
* @}
*/


/***  Copyright (C) 2021 wy1107412334@163.com All rights reserved ***/
/****************************** END OF FILE *************************/


